// The main code of PCPSS
// Copyright (C) 2019 dumaosen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
    #include <windows.h>
#elif __unix__
    #include <unistd.h>
#endif

// Data path
#define datapath "data"
#define get_data_path(str) datapath#str
// If theme is set
bool iftheme;
// Theme path
char* themepath;

// Check the num-th label exists
bool check_label(const char* path, int num)
{
    FILE *file = fopen(path, "r");
    char file_char = 0;
    while(file_char != EOF)
    {
        if(!num)
            return true;
        if(file_char == '!')
        {
            while(file_char == '!')
            {
                file_char = fgetc(file);
                if(file_char == 'l')
                    num --;
            }
        }
        else
            file_char = fgetc(file);
    }
    fclose(file);
    return false;
}

// Get the num-th label address
FILE* get_label(const char* path, int num)
{
    FILE *file = fopen(path, "r");
    char file_char = 0;
    while(file_char != EOF)
    {
        if(!num)
            return file;
        if(file_char == '!')
        {
            while(file_char == '!')
            {
                file_char = fgetc(file);
                if(file_char == 'l')
                    num --;
            }
        }
        else
            file_char = fgetc(file);
    }
    printf("\n ERROR: LABEL %d IN FILE %s NOT FOUND\n", num, path);
    fclose(file);
    exit(1);
}

// Read file
// Num should < 4 to give space to print_theme
const char* read_file(FILE *start, int num)
{    
    FILE *file = start;
    char file_char = fgetc(file);
    static char read[5][128];
    memset(read[num], 0, sizeof(read[num]));
    int i;
    for(i = 0; file_char != EOF; i++)
    {
        if(file_char == '!')
        {
            while(file_char == '!')
            {
                file_char = fgetc(file);
                if(file_char == 'l')
                {
                    fclose(file);
                    return read[num];
                }
                read[num][i] = '!';
                i++;
            }
        }
        else
            read[num][i] = file_char;
        file_char = fgetc(file);
    }
    fclose(file);
    return read[num];
}

// Set theme file path
void set_theme_path(char* path)
{
    if(!iftheme)
    {
        iftheme = true;
        themepath = path;
    }
}

// Read an int from a file
int read_int(FILE *start)
{
    int get;
    fscanf(start, "%d", &get);
    fclose(start);
    return get;
}

// Write an int to a file
void write_int(const char* path, int num)
{
    FILE *file = fopen(path, "w");
    fprintf(file, "%d", num);
    fclose(file);
}

// Write an int in "a" mode
void write_int_a(const char* path, int num)
{
    FILE *file = fopen(path, "a");
    fprintf(file, " !l%d", num);
    fclose(file);
}

// Clear a file
void clear_file(const char* path)
{
    FILE *file = fopen(path, "w");
    fclose(file);
}

// Print an int from a file
void print_int(FILE *start)
{
    int get;
    fscanf(start, "%d", &get);
    printf("%d", get);
    fclose(start);
}

// Color chars
const char colorset[] = "rygcbmw";

// Print color
void print_color(const char mark)
{
    switch(mark)
    {
        case 'r':
            // Print red
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_RED);
            #elif __unix__
                printf("\033[31m");
            #endif
            break;
        case 'y':
            // Print yellow
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
            #elif __unix__
                printf("\033[32m");
            #endif
            break;
        case 'g':
            // Print green
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_GREEN);
            #elif __unix__
                printf("\033[33m");
            #endif
            break;
        case 'c':
            // Print cyan
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE);
            #elif __unix__
                printf("\033[36m");
            #endif
            break;
        case 'b':
            // Print blue
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_BLUE);
            #elif __unix__
                printf("\033[34m");
            #endif
            break;
        case 'm':
            // Print magenta
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE);
            #elif __unix__
                printf("\033[35m");
            #endif
            break;
        case 'w':
            // Print white
            #ifdef _WIN32
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                FOREGROUND_INTENSITY);
            #elif __unix__
                printf("\033[0m");
            #endif
            break;
    }
}

// Print color from theme file
void print_theme(const char mark)
{
    int i;
    for(i = 0; i < 7; i ++)
        if(mark == colorset[i])
        {
            if(!iftheme)
                print_color(mark);
            else
                print_color(*read_file(get_label(themepath, i), 4));
        }
}

// Print file with mark
void print_file(FILE *start)
{
    char file_char = fgetc(start);
    while(file_char != EOF)
    {
        bool ifmark = file_char == '!';
        while(file_char == '!')
        {
            file_char = fgetc(start);
            switch(file_char)
            {
                case '!':
                    printf("!");
                    continue;
                case 'l':
                    fclose(start);
                    return;
                case 'r':
                case 'y':
                case 'g':
                case 'c':
                case 'b':
                case 'm':
                case 'w':
                    print_theme(file_char);
                    break;
                default:
                    printf("!");
                    if(file_char != EOF)
                        printf("%c", file_char);
            }
        }
        if(!ifmark)
            printf("%c", file_char);
        file_char = fgetc(start);
    }
    fclose(start);
    return;
}

// Pause for a while
void sleep_func(int a)
{
    #ifdef _WIN32
        Sleep(1000 * a);
    #elif __unix__
        sleep(a);
    #endif
}

// Clear the screen
void clear_func()
{
    #ifdef _WIN32
        system("CLS");
    #elif __unix__
        system("clear");
    #endif
}

// List 3 gamemodes to make code more readable
enum modes {adventure, battle, endless};

// List 3 pss, the same reason
enum pss {stone, scissors, paper};

// Square root int version
int sqrt_int(int a)
{
    if(a < 0)
        return 0;
    int l = 0, r = a, res = (l + r) / 2;
    while(l != r) {
        if(res * res < a)
            l = res + 1;
        else if(res * res == a)
            return res;
        else
            r = res;
        res = (l + r) / 2;
    }
    return res;
}

// Convert numbers to human language
const char* language_converter(enum pss a)
{
    const char* path = get_data_path(/texts/pss.txt);
    switch(a)
    {
        case stone:
            return read_file(get_label(path, 0), 0);
        case scissors:
            return read_file(get_label(path, 1), 0);
        case paper:
            return read_file(get_label(path, 2), 0);
        default:
            return read_file(get_label(path, 3), 0);
    }
}

// Change hp color on hpboard depend on its value
void hp_color(int a, int maxa)
{
    if(a < maxa / 2)
        print_theme('r');
    else if(a < maxa)
        print_theme('y');
    else
        print_theme('g');
}

// Limit hp value to fit hpboard
void limit(int *a)
{
    if(*a > 99)
        *a = 99;
    if(*a < 0)
        *a = 0;
}

// Calc level by exp
int get_level()
{
	int a = read_int(get_label(get_data_path(/info/exp.txt), 0));
    return sqrt_int(a / 100) + 1;
}

// Print title file
void print_title()
{
    print_file(get_label(get_data_path(/texts/title.txt), 0));
}

// Print error file
void print_error()
{
    printf("\a");
    print_file(get_label(get_data_path(/texts/error.txt), 0));
}

// Print help file
void print_help()
{
    print_file(get_label(get_data_path(/texts/help.txt), 0));
}

// Print credit file
void print_credit()
{
    print_file(get_label(get_data_path(/texts/credit.txt), 0));
}

// Print player info
void print_info()
{
    const char* path = get_data_path(/texts/info.txt);
    print_file(get_label(path, 0));
    print_int(get_label(get_data_path(/info/besta.txt), 0));
    print_file(get_label(path, 1));
    print_int(get_label(get_data_path(/info/beste.txt), 0));
    print_file(get_label(path, 2));
    print_int(get_label(get_data_path(/info/exp.txt), 0));
    print_file(get_label(path, 3));
    printf("%d\n", get_level());
    print_theme('w');
}

// Print hpboard file
void print_hpboard(int a, int b, int inita, int initb,
                   enum modes gamemode, int score)
{
    const char* path = get_data_path(/texts/hpboard.txt);
    print_file(get_label(path, 0));
    hp_color(a, inita);
    printf("%.2d", a);
    print_file(get_label(path, 1));
    // In endless mode, don't print the hp of PC
    if(gamemode == endless)
    {
        print_theme('c');;
        printf("--");
    }
    else
    {
        hp_color(b, initb);
        printf("%.2d", b);
    }
    print_file(get_label(path, 2));
    // In endless and adventure mode, print the score
    if(gamemode != battle)
    {
        print_file(get_label(path, 3));
        printf("%d\n\n", score);
        print_theme('w');
    }
}

// Print player state
void print_state(bool state)
{
    const char* path = get_data_path(/texts/state.txt);
    if(state)
        print_file(get_label(path, 0));
    else
        print_file(get_label(path, 1));
}

// Print the pss of each player
void print_pss(enum pss a, enum pss b)
{
    const char* path = get_data_path(/texts/pssboard.txt);
    print_file(get_label(path, 0));
    printf("%s", language_converter(a));
    print_file(get_label(path, 1));
    printf("%s", language_converter(b));
    print_file(get_label(path, 2));
}

// Print the end message
void summary(int a, int b)
{
    const char* path = get_data_path(/texts/summary.txt);
    if(a < b)
        print_file(get_label(path, 0));
    else if(a == b)
        print_file(get_label(path, 1));
    else
        print_file(get_label(path, 2));
}

// Write info
void write_info(enum modes gamemode, int score)
{
    const char* path1 = get_data_path(/info/exp.txt);
    const char* path2 = get_data_path(/texts/infomessage.txt);
    char* path3 = get_data_path(/info/besta.txt);
    if(gamemode == endless)
        path3 = get_data_path(/info/beste.txt);
    int oldexp = read_int(get_label(path1, 0));
    switch(gamemode)
    {
        case adventure:
            write_int(path1, oldexp + score * score * 4);
            break;
        case battle:
            write_int(path1, oldexp + score * 5 + 25);
            break;
        case endless:
            write_int(path1, oldexp + score * 2);
            break;
    }
    if(sqrt_int(oldexp / 100) + 1 < get_level())
        print_file(get_label(path2, 1));
    if(read_int(get_label((const char*)path3, 0)) < score && gamemode != battle)
    {
        print_file(get_label(path2, 0));
        printf("%d\n", score);
        write_int((const char*)path3, score);
    }
    print_theme('w');
}

// Write pause file
void write_pause(enum modes gamemode, int initmk, int initpc,
                   int score, int mk, int pc, bool state)
{
    const char* path = get_data_path(/pause.txt);
    write_int(path, (int)gamemode);
    write_int_a(path, initmk);
    write_int_a(path, initpc);
    write_int_a(path, score);
    write_int_a(path, mk);
    write_int_a(path, pc);
    write_int_a(path, (int)state);
}

// Determine if player a wins player b in a round
bool compare(enum pss a, enum pss b)
{
    if((a - b == 1 || a - b == -2))
        return false;
    return true;
}

// Malloc spaces and read command
// NOTE: Free the spaces after using it
char* enter_command(int len)
{
    char* command;
    command = (char*)malloc(len);
    print_file(get_label(get_data_path(/texts/command.txt), 0));
    scanf("%s", command);
    return command;
}

// Calculate the hp of each player
void calc_hp(bool win, bool state, int *player,
             const char* name, int hp, enum modes gamemode)
{
    const char* path = get_data_path(/texts/hp.txt);
    const int hurt = rand() % (hp / 5) + hp / 5 + 3;
    if(!win && !state)
        print_theme('g');
    else if(win)
        print_theme('y');
    else
        print_theme('r');
    if(win)
    {
        *player += hurt / 3;
        print_file(get_label(path, 1));
        printf("%s", name);
        print_file(get_label(path, 2));
        printf("%d\n", hurt / 3);
    }
    else
    {
        *player -= hurt;
        print_file(get_label(path, 3));
        printf("%s", name);
        print_file(get_label(path, 4));
        printf("%d\n", hurt);
    }
    // In endless mode, don't limit PC's hp.
    if(gamemode != endless)
        limit(player);
    print_theme('w');
}

// Main loop
void game(enum modes gamemode, int initmk, int initpc,
           int score, int mk, int pc, bool state)
{
    if(gamemode != endless)
    {
        limit(&initpc);
        limit(&pc);
    }
    limit(&initmk);
    limit(&mk);
    clear_func();
    int *player = &initmk;
    enum pss mkhand, pchand;
    print_hpboard(mk, pc, initmk, initpc, gamemode, score);
    print_state(state);
    for(;;)
    {
        // Read command
        char* command = enter_command(1);
        switch(*command)
        {
            case '0':
                mkhand = stone;
                break;
            case '2':
                mkhand = scissors;
                break;
            case '5':
                mkhand = paper;
                break;
            case 'q':
                clear_func();
                print_hpboard(mk, pc, initmk, initpc, gamemode, score);
                // In battle mode, print who wins
                if(gamemode == battle)
                    summary(pc, mk);
                else
                    write_info(gamemode, score);
                sleep_func(2);
                return;
            case 'p':
                write_pause(gamemode, initmk, initpc, score, mk, pc, state);
                exit(0);
            case 'h':
                print_help();
                continue;
            case 'c':
                print_credit();
                continue;
            case 'i':
                print_info();
                continue;
            default:
                print_error();
                continue;
        }
        free(command);
        pchand = rand() % 3;
        // Print AI command
        print_file(get_label(get_data_path(/texts/command.txt), 1));
        printf("%d\n", pchand == 2 ? 5 : pchand == 1 ? 2 : 0);
        sleep_func(1);
        print_pss(mkhand, pchand);
        const char* path = get_data_path(/texts/players.txt);
        bool win = compare(mkhand, pchand);
        // In endless mode, player only points to mankind
        if(gamemode != endless)
            player = win ? &initmk : &initpc;
        // Change state if the same command
        if(pchand == mkhand)
        {
            state = !state;
            print_file(get_label(get_data_path(/texts/hp.txt), 0));
        }
        // Calculate players' hp
        else if(state)
            calc_hp(win, state, &mk, read_file(get_label(path, 1), 0),
                    *player, gamemode);
        else
            calc_hp(!win, state, &pc, read_file(get_label(path, 0), 0),
                    *player, gamemode);
        // In endless mode, limit mankind and make score positive
        if(gamemode == endless)
        {
            limit(&mk);
            score = INT_MAX - pc >= 0 ? INT_MAX - pc : 0;
            pc = INT_MAX - pc >= 0 ? pc : INT_MAX;
        }
        print_theme('w');
        sleep_func(1);
        clear_func();
        print_hpboard(mk, pc, initmk, initpc, gamemode, score);
        if(!pc || !mk)
        {
            // In endless mode with PC's hp = 0, continue game
            if(gamemode == endless && mk)
            {
                print_state(state);
                continue;
            }
            // In adventure and battle mode, print who wins
            if(gamemode != endless)
                summary(pc, mk);
            if(gamemode == endless || (gamemode == adventure && !mk) ||
              (gamemode == battle && mk))
                write_info(gamemode, score);
            sleep_func(2);
            clear_func();
            if(gamemode == adventure && mk)
                game(adventure, initmk, initpc + 2, score + 1,
                     initmk, initpc + 2, state);
            return;
        }
        print_state(state);
    }
}

// Check paused game exists
bool check_pause()
{
	if(!check_label(get_data_path(/pause.txt), 6))
	{
		print_file(get_label(get_data_path(/texts/error.txt), 1));
		return false;
	}
	return true;
}

// Start a paused game
void start_pause()
{
    const char* path = get_data_path(/pause.txt);
    enum modes gamemode = (enum modes)read_int(get_label(path, 0));
    int initmk = read_int(get_label(path, 1));
    int initpc = read_int(get_label(path, 2));
    int score = read_int(get_label(path, 3));
    int mk = read_int(get_label(path, 4));
    int pc = read_int(get_label(path, 5));
    bool state = (bool)read_int(get_label(path, 6));
    clear_file(path);
    game(gamemode, initmk, initpc, score, mk, pc, state);
}

// Start menu
int main()
{
    set_theme_path(get_data_path(/theme.txt));
    srand((unsigned)time(NULL));
    clear_func();
    
    print_title();
    for(;;)
    {
        char* command = enter_command(1);
        switch(*command)
        {
            case 'q':
                clear_func();
                exit(0);
            case 'a':
                game(adventure, 28 + get_level() * 2, 6, 0, 
                      28 + get_level() * 2, 6, false);
                clear_func();
                print_title();
                break;
            case 'b':
                // Use get_level() as score to calc exp
                game(battle, 28 + get_level() * 2, 28 + get_level() * 2, get_level(),
                      28 + get_level() * 2, 28 + get_level() * 2, false);
                clear_func();
                print_title();
                break;
            case 'e':
                game(endless, 28 + get_level() * 2, INT_MAX, 0,
                      28 + get_level() * 2, INT_MAX, false);
                clear_func();
                print_title();
                break;
            case 'p':
				if(!check_pause())
					continue;
                start_pause();
                clear_func();
                print_title();
                break;
            case 'h':
                print_help();
                break;
            case 'c':
                print_credit();
                break;
            case 'i':
                print_info();
                break;
            default:
                print_error();
        }
        free(command);
    }
}
